package pf;

import java.nio.channels.FileChannel;

import basic.ReturnCode;


public class PF_HashTable implements PF_HashTable_Interface {

	private static final ReturnCode PF_HASHNOTFOUND = null;
	private static final ReturnCode PF_HASHPAGEEXIST = null;
	private static final ReturnCode PF_NOMEM = null;
	int numBuckets;          	// Number of hash table buckets
	PF_HashEntry[] hashTable;  	// Hash table
	int slot;

	// Hash function
	private int Hash(FileChannel fd, int pageNum){ 

		return ((fd.hashCode() + pageNum) % numBuckets); 
	}   

	//
	// PF_HashTable
	//
	// Desc: Constructor for PF_HashTable object, which allows search, insert,
	//	       and delete of hash table entries.
	// In:   numBuckets - number of hash table buckets
	//
	public PF_HashTable(int _numBuckets) {

		// Initialize numBuckets local variable from parameter
		numBuckets = _numBuckets;

		// Allocate memory for hash table
		hashTable = new PF_HashEntry[numBuckets];

		// Initialize all buckets to empty
		for (int i = 0; i < numBuckets; i++)
			hashTable[i] = null;
	}

	//
	// Find
	//
	// Desc: Find a hash table entry.
	// In:   fd - file descriptor
	//	       pageNum - page number
	// Out:  slot - set to slot associated with fd and pageNum
	// Ret:  PF return code
	//
	public ReturnCode Find(FileChannel fd, int pageNum, int slot) {
		
		// Get which bucket it should be in
		int bucket = Hash(fd, pageNum);

		if (bucket<0)
			return (PF_HASHNOTFOUND);

		// Go through the linked list of this bucket
		for (PF_HashEntry entry = hashTable[bucket];
		entry != null;
		entry = entry.next) {
			if (entry.fd.equals(fd) && entry.pageNum == pageNum) {

				// Found it
				this.slot = entry.slot;
				return ReturnCode.PF_SUCCESS;
			}
		}

		// Didn't find it
		return (PF_HASHNOTFOUND);
	}

	//
	// Insert
	//
	// Desc: Insert a hash table entry
	// In:   fd - file descriptor
	//	       pagenum - page number
	//	       slot - slot associated with fd and pageNum
	// Ret:  PF return code
	//
	public ReturnCode Insert(FileChannel fd, int pageNum, int slot) {
		
		// Get which bucket it should be in
		int bucket = Hash(fd, pageNum);

		// Check entry doesn't already exist in the bucket
		PF_HashEntry entry;
		  for (entry = hashTable[bucket];
		       entry != null;
		       entry = entry.next) {
		    if (entry.fd.equals(fd) && entry.pageNum == pageNum)
		      return (PF_HASHPAGEEXIST);
		  }

		// Allocate memory for new hash entry
		entry = new PF_HashEntry();
		
		// Insert entry at head of list for this bucket
		entry.fd = fd;
		entry.pageNum = pageNum;
		entry.slot = slot;
		entry.next = hashTable[bucket];
		entry.prev = null;
		if (hashTable[bucket] != null)
			hashTable[bucket].prev = entry;
		hashTable[bucket] = entry;

		// Return ok
		return ReturnCode.PF_SUCCESS;
	}

	//
	// Delete
	//
	// Desc: Delete a hash table entry
	// In:   fd - file descriptor
	//	       pagenum - page number
	// Ret:  PF return code
	//
	public ReturnCode Delete(FileChannel fd, int pageNum) {
		
		// Get which bucket it should be in
		int bucket = Hash(fd, pageNum);

		// Find the entry is in this bucket
		PF_HashEntry entry;
		for (entry = hashTable[bucket];
		entry != null;
		entry = entry.next) {
			if (entry.fd.equals(fd) && entry.pageNum == pageNum)
				break;
		}

		// Did we find hash entry?
		if (entry == null)
			return (PF_HASHNOTFOUND);

		// Remove this entry
		if (entry == hashTable[bucket])
			hashTable[bucket] = entry.next;
		if (entry.prev != null)
			entry.prev.next = entry.next;
		if (entry.next != null)
			entry.next.prev = entry.prev;

		entry = null;
		
		// Return ok
		return ReturnCode.PF_SUCCESS;
	}

}
